package com.hyt.it.ogt.kq.service.gov.arrange;

import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Maps;
import com.hyt.core.base.BaseEntity;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.kq.common.config.exception.KqException;
import com.hyt.it.ogt.kq.common.gov.constant.ArrangeParamCodeEnum;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.constant.StringCodeEnum;
import com.hyt.it.ogt.kq.common.gov.utils.StringUtil;
import com.hyt.it.ogt.kq.service.gov.mapper.ExamAreaMapper;
import com.hyt.it.ogt.kq.service.gov.mapper.TimeRoomCandidateMapper;
import com.hyt.it.ogt.kq.service.gov.model.entity.*;
import com.hyt.it.ogt.kq.service.gov.model.vo.CandidateArrangePreVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.CandidateArrangeSubjectPreVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.TaskPlaceVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.TimeRoomVO;
import com.hyt.it.ogt.kq.service.gov.service.*;
import com.hyt.progress.service.ProgressManager;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 按考区编排
 */
@Component
public class ArrangeByArea {
    @Resource
    private AdmissionService admissionService;
    @Resource
    private TimeRoomCandidateMapper timeRoomCandidateMapper;
    @Resource
    private ITimeSubjectService timeSubjectService;
    @Resource
    private ITimeRoomCandidateService iTimeRoomCandidateService;
    @Resource
    private ITimeRoomService iTimeRoomService;
    @Resource
    private ICandidateSubjectService iCandidateSubjectService;
    @Resource
    private ICandidateService iCandidateService;
    @Resource
    private ICustomArrangeService iCustomArrangeService;
    @Resource
    private ITaskPlaceService iTaskPlaceService;
    @Resource
    private ITaskRoomService iTaskRoomService;
    @Resource
    private ExamAreaMapper examAreaMapper;
    @Resource
    private ITimeService iTimeService;

    public void arrange(Map<String, String> paramMap, String taskId, boolean deleteStatus) throws KqException {
        //否删除数据重新编排，如果是编排页面，则删除，如果是在考生未编排的列表编排，则不用删除
        if (deleteStatus) {
            //重新编排的情况下，删除所有编排数据
            timeRoomCandidateMapper.deleteArrangeCandidateByTaskId(taskId);
        }
        ProgressManager.updateProgress(10L);
        //4.获取未编排的考生数据，一个考生多个科目共一条数据
        Map<String, CandidateArrangePreVO> candidateMap = iCandidateSubjectService.getCandidateNeedArrange(taskId);

        //5.校验，如果按区编排，则考生必须有区域信息。
        if (String.valueOf(ArrangeParamCodeEnum.ARRANGE_TYPE_AREA.getCode()).equals(paramMap.get(StringCodeEnum.ARRANGE_PARAM_ARRANGE_TYPE.getCode()))) {
            iCandidateService.checkCandidateAreaThrow(taskId);
        }
        ProgressManager.updateProgress(17L);

        //6.获取科次信息--批次科目
        Map<String, String> timeSubjectMap = timeSubjectService.getTimeSubjectMap(taskId);
        if (timeSubjectMap == null || timeSubjectMap.isEmpty()) {
            throw new KqException(ResponseCode.ARRANGE_CANDIDATE_NOT_TIME_ROOM.getCode(), ResponseCode.ARRANGE_CANDIDATE_NOT_TIME_ROOM.getMsg());
        }
        ProgressManager.updateProgress(23L);
        //7.获取考点数据：原则考生编排到同一个考点，不要编排到多个考点去
        List<TaskPlaceVO> taskPlaceVOList = iTaskPlaceService.getTaskPlaceList(taskId);
        if (taskPlaceVOList == null || taskPlaceVOList.isEmpty()) {
            throw new KqException(ResponseCode.ARRANGE_CANDIDATE_NOT_ROOM.getCode(), ResponseCode.ARRANGE_CANDIDATE_NOT_ROOM.getMsg());
        }
        ProgressManager.updateProgress(30L);
        //8.考区查询
        List<String> examAreaList = examAreaMapper.selectAreaAndStudentAreaCode(taskId);
        //获取考生准考证号数据
        List<CustomArrange> customArrangeList = null;
        Integer admissionType = new Integer(paramMap.get("admissionType"));
        if (ArrangeParamCodeEnum.ADMISSION_TYPE_CUSTOM.getCode().equals(admissionType)) {
            //使用自定义准考证号编排
            customArrangeList = iCustomArrangeService.listByTaskId(taskId);
        }

        // 查询所有免排考场
        Map<String, String> ignoreTimeRoomMap = iTimeRoomService.lambdaQuery().eq(TimeRoom::getTaskId, taskId)
                .eq(TimeRoom::getSelectType, 1).eq(BaseEntity::getDelFlag, false).list()
                .stream().collect(Collectors.toMap((e -> e.getTimeId() + "_" + e.getRoomId()), TimeRoom::getRoomId));

        List<Time> timeList = iTimeService.lambdaQuery().eq(Time::getTaskId, taskId).eq(BaseEntity::getDelFlag, false).list();
        List<TaskRoom> roomList = iTaskRoomService.lambdaQuery().eq(TaskRoom::getTaskId, taskId).eq(TaskRoom::getDelFlag, false).list();
        Map<String, String> timeRoomIdMap = new HashMap<>();
        for(Time time : timeList){
            for (TaskRoom taskRoom : roomList) {
                String timeRoomId = UUIDUtils.newSortUUID();
                timeRoomIdMap.put(time.getId()+"_"+taskRoom.getId(), timeRoomId);
            }
        }
        ProgressManager.updateProgress(65L);
        this.arrangeCandidate(taskId, taskPlaceVOList, candidateMap, ignoreTimeRoomMap, paramMap, timeSubjectMap, examAreaList, customArrangeList, timeList,timeRoomIdMap);
        ProgressManager.updateProgress(95L);
    }

    /**
     * 编排--全国随机编排
     * 一个考生多个科目必须编排在一个考点，否则该考生所有科目编排不成功
     * @param taskId
     * @param taskPlaceVOList
     * @param candidateMap
     * @param paramMap
     * @param timeSubjectMap
     */
    private void arrangeCandidate(String taskId, List<TaskPlaceVO> taskPlaceVOList, Map<String, CandidateArrangePreVO> candidateMap,
                                  Map<String, String> ignoreTimeRoomMap, Map<String, String> paramMap, Map<String, String> timeSubjectMap,
                                  List<String> examAreaList, List<CustomArrange> customArrangeList, List<Time> timeList, Map<String, String> timeRoomIdMap) {
        //编排成功的考生，需要入库
        List<TimeRoomCandidate> timeRoomCandidateAllList = new ArrayList<>();
        //6 考点开始编排，
        taskPlaceVOList.stream().forEach(taskPlaceVO -> {
            //6.1 考点下所有的未编排的场次数据
            if (taskPlaceVO.getTimeRoomVOList() == null || taskPlaceVO.getTimeRoomVOList().isEmpty()) {
                taskPlaceVO.setTimeRoomVOList(this.selectTimeRoomByPlaceId(taskId, taskPlaceVO.getId(), ignoreTimeRoomMap, timeList,timeRoomIdMap));
            }

            //场次数据需要更新入库
            Map<String, Integer> timeRoomActualCountMap = Maps.newHashMap();

            //6.2 遍历考生
            candidateMap.forEach((candidateId, candidateArrangePreVO) -> {

                //6.2.0、编排方式，如果是混编，则不需要匹配考区，如果是按区编排，则一依次匹配县区，市，省，考区等循序编排
                if (!getArrangeStatus(paramMap, candidateArrangePreVO, taskPlaceVO, examAreaList)) {
                    return;
                }

                //考生临时编排数据
                List<TimeRoomCandidate> tempTimeRoomCandidateList = new ArrayList<>();
                //批次临时编排数据
                Map<String, String> tempTimeMap = Maps.newHashMap();
                //遍历科目-编排到场次去
                List<CandidateArrangeSubjectPreVO> candidateArrangeSubjectPreVOList = candidateArrangePreVO.getCandidateArrangeSubjectPreVOList();
                //6.2.1 遍历考生下，所有的科目
                candidateArrangeSubjectPreVOList.stream().forEach(candidateArrangeSubjectPreVO -> {
                    //6.2.1.1 遍历该考点下所有的场次
                    taskPlaceVO.getTimeRoomVOList().stream().anyMatch(timeRoomVO -> {
                        //1、如果这个批次已编排过，则跳过本次循环，寻找下个场次
                        if (tempTimeMap.containsKey(timeRoomVO.getTimeId())) {
                            return false;
                        }

                        //2、如果实际编排数和考场的最大编排书，意味着已编满，无需再编排，则跳过本次循环，寻找下个场次
                        if (timeRoomVO.getActualCount().intValue() >= timeRoomVO.getMaxCount().intValue()) {
                            return false;
                        }

                        //3、如果该考生科目，在当前的批次下找不到，则跳过本次循环，寻找下个场次
                        String timeSubject = StringUtils.join(timeRoomVO.getTimeId(), "_", candidateArrangeSubjectPreVO.getSubjectId());
                        if (!timeSubjectMap.containsKey(timeSubject)) {
                            return false;
                        }

                        //4、如果考生的考试类型(电子作答、纸笔作答)和考场的考试类型不一致，则跳过本次循环，寻找下个场次
                        if (timeRoomVO.getAnswerType().intValue() != candidateArrangeSubjectPreVO.getAnswerType().intValue()) {
                            return false;
                        }

                        //5、开始编排，将编排的批次添加到临时批次编排中，下个科目再次编排则不能编排到本批次
                        tempTimeMap.put(timeRoomVO.getTimeId(), "");

                        //6、开始编排，组装考生临时编排数据，不包含座位号和准考证号，确认要编排时，再添加。
                        TimeRoomCandidate timeRoomCandidate = new TimeRoomCandidate();
                        timeRoomCandidate.setCandidateId(candidateId)
                                .setTaskId(taskId)
                                .setRoomId(timeRoomVO.getRoomId())
                                .setTimeRoomId(StrUtil.isNotBlank(timeRoomIdMap.get(timeRoomVO.getTimeId()+"_"+timeRoomVO.getRoomId())) ?
                                        timeRoomIdMap.get(timeRoomVO.getTimeId()+"_"+timeRoomVO.getRoomId()) : UUIDUtils.newSortUUID())
                                .setTimeId(timeRoomVO.getTimeId())
                                .setPlaceId(taskPlaceVO.getId())
                                .setSubjectId(candidateArrangeSubjectPreVO.getSubjectId())
                                .setCandidateSubjectId(candidateArrangeSubjectPreVO.getCandidateSubjectId())
                                .setId(UUIDUtils.newSortUUID());

                        tempTimeRoomCandidateList.add(timeRoomCandidate);
                        return true;
                    });
                });

                //6.2.2 单个考生成功编排，含多个科目
                if (candidateArrangeSubjectPreVOList.size() == tempTimeRoomCandidateList.size()) {
                    //6.2.2.1 遍历临时编排数据
                    tempTimeRoomCandidateList.stream().forEach(timeRoomCandidate -> {
                        //准考证号
                        Integer admissionType = new Integer(paramMap.get(StringCodeEnum.ARRANGE_PARAM_ADMISSION_TYPE.getCode()));
                        if (ArrangeParamCodeEnum.ADMISSION_TYPE_CUSTOM.getCode().equals(admissionType)) {
                            //使用自定义准考证号编排
                            String admissionNumber = customArrangeList.stream()
                                    .filter(candidateAdmissionNumber -> timeRoomCandidate.getCandidateId()
                                            .equals(candidateAdmissionNumber.getCandidateId()))
                                    .findFirst()
                                    .map(CustomArrange::getAdmissionNumber)
                                    .orElseThrow(() -> new KqException(ResponseCode.ARRANGE_CANDIDATE_ADMISSION_NUMBER_NOT_FOUND.getCode(),
                                            ResponseCode.ARRANGE_CANDIDATE_ADMISSION_NUMBER_NOT_FOUND.getMsg()));
                            timeRoomCandidate.setAdmissionNum(admissionNumber);
                        } else {
                            //使用准考证号生成规则生成准考证号
                            String admissionNum = admissionService.getAdmissionNumber(taskId, paramMap, taskPlaceVO);
                            timeRoomCandidate.setAdmissionNum(admissionNum);
                        }
                        //场次编排人数更新
                        taskPlaceVO.getTimeRoomVOList().stream().anyMatch(timeRoomVO -> {
                            if (timeRoomCandidate.getTimeRoomId().equals(timeRoomVO.getId())) {
                                //要在actualCount增加之前调用
                                String seatNum = getSeatNum(timeRoomVO);
                                timeRoomCandidate.setSeatNum(seatNum);

                                //actualCount增加
                                timeRoomVO.setActualCount(timeRoomVO.getActualCount() + 1);

                                timeRoomActualCountMap.put(timeRoomVO.getId(), timeRoomVO.getActualCount());

                                return true;
                            }
                            return false;
                        });
                    });

                    //6.2.2.2 将考生临时编排数据放入到编排数据中，等待入库
                    timeRoomCandidateAllList.addAll(tempTimeRoomCandidateList);
                    //6.2.2.3 将成功编排的考生从考生编排中置空，等待下个考点编排时删除
                    candidateMap.put(candidateId, null);
                }

                //6.2.3 考生编排数据入库，10000条入库一次
                int maxData = 10000;
                if (timeRoomCandidateAllList.size() > maxData) {
                    boolean flag = iTimeRoomCandidateService.saveBatch(timeRoomCandidateAllList);
                    if (flag) {
                        timeRoomCandidateAllList.clear();
                    }
                }
            });

            //6.3 更新编排的考生数据
            boolean flag = iTimeRoomCandidateService.saveBatch(timeRoomCandidateAllList);
            if (flag) {
                timeRoomCandidateAllList.clear();
            }

            //6.4 更新场次的实际编排数
            if (timeRoomActualCountMap != null && !timeRoomActualCountMap.isEmpty()) {
                //6.5 将编排的考生从未编排的考生数据中删除
                candidateMap.values().removeIf(v -> v == null);
            }
            //所有考生循环结束==================
        });
        //7 再次编排--如果是按区域编排，则扩大编排范围：区域，市，省，考区。
        this.forEachArrangeCandidate(taskId, taskPlaceVOList, candidateMap, ignoreTimeRoomMap, paramMap, timeSubjectMap, examAreaList, customArrangeList, timeList, timeRoomIdMap);
    }

    /**
     * 是否循环编排考生
     */
    private void forEachArrangeCandidate(String taskId, List<TaskPlaceVO> taskPlaceVOList, Map<String, CandidateArrangePreVO> candidateMap,
                                         Map<String, String> ignoreTimeRoomMap, Map<String, String> paramMap,
                                         Map<String, String> timeSubjectMap, List<String> examAreaList,
                                         List<CustomArrange> customArrangeList, List<Time> timeList, Map<String, String> timeRoomIdMap) {
        //如果是混编，不需要循环执行
        if (String.valueOf(ArrangeParamCodeEnum.ARRANGE_TYPE_MIX.getCode()).equals(
                paramMap.get(StringCodeEnum.ARRANGE_PARAM_ARRANGE_TYPE.getCode()))) {
            return;
        }

        int auto = Integer.parseInt(String.valueOf(paramMap.get("areaArrangeAuto")));
        if (auto < ArrangeParamCodeEnum.AREA_ARRANGE_LEVEL_EXAM_AREA.getCode().intValue()) {
            paramMap.put("areaArrangeAuto", String.valueOf(++auto));
            this.arrangeCandidate(taskId, taskPlaceVOList, candidateMap, ignoreTimeRoomMap, paramMap, timeSubjectMap, examAreaList, customArrangeList, timeList, timeRoomIdMap);
        }
    }

    /**
     * 区域查找
     *
     * @param paramMap
     * @param candidateArrangePreVO
     * @param taskPlaceVO
     * @return
     */
    private boolean getArrangeStatus(Map<String, String> paramMap, CandidateArrangePreVO candidateArrangePreVO, TaskPlaceVO taskPlaceVO, List<String> examAreaList) {
        //2、编排方式，如果是混编，则不需要匹配考区，如果是按区编排，则一次匹配县区，市，省，考区等循序编排
        String areaArrangeAuto = paramMap.get("areaArrangeAuto");

        //2.1 按区域编排
        if (areaArrangeAuto.equals(ArrangeParamCodeEnum.AREA_ARRANGE_LEVEL_DISTRICT.getCode().toString())) {
            if (!candidateArrangePreVO.getDistrict().equals(taskPlaceVO.getDistrict())) {
                return false;
            }
        }

        //2.2 按市编排
        if (areaArrangeAuto.equals(ArrangeParamCodeEnum.AREA_ARRANGE_LEVEL_CITY.getCode().toString())) {
            if (!candidateArrangePreVO.getCity().equals(taskPlaceVO.getCity())) {
                return false;
            }
        }

        //2.3 按省编排
        if (areaArrangeAuto.equals(ArrangeParamCodeEnum.AREA_ARRANGE_LEVEL_PROVINCE.getCode().toString())) {
            if (!candidateArrangePreVO.getProvince().equals(taskPlaceVO.getProvince())) {
                return false;
            }
        }

        //2.4 按考区编排
        if (areaArrangeAuto.equals(ArrangeParamCodeEnum.AREA_ARRANGE_LEVEL_EXAM_AREA.getCode().toString())) {
            String candidateExamArea = StringUtils.join(taskPlaceVO.getProvince(), "_", candidateArrangePreVO.getProvince());

            if (!examAreaList.contains(candidateExamArea)) {
                return false;
            }
        }

        return true;
    }

    /**
     * 获取座位号
     *
     * @param timeRoomVO
     * @return
     */
    private String getSeatNum(TimeRoomVO timeRoomVO) {
        //1.组装座位号
        int seatNum = timeRoomVO.getActualCount() + 1;
        if (seatNum > timeRoomVO.getMaxCount()) {
            //座位号大于最大编排数，说明中间后空缺的座位号，则去查库
            List<String> seatNumList = timeRoomCandidateMapper.selectArrangeCandidateSeatNum(timeRoomVO);
            for (int i = 0; i < seatNumList.size(); i++) {
                int num = Integer.parseInt(seatNumList.get(i));
                int numAuto = i + 1;
                if (numAuto != num) {
                    seatNum = numAuto;
                    break;
                }
            }
        }

        return StringUtil.stringFormatZero(seatNum, 3);
    }

    private List<TimeRoomVO> selectTimeRoomByPlaceId(String taskId, String placeId, Map<String, String> ignoreTimeRoomMap, List<Time> timeList,Map<String,String> timeRoomIdMap) {
        List<TimeRoomVO> timeRoomVOList = new ArrayList<>();
        List<TaskRoom> taskRoomList = iTaskRoomService.lambdaQuery().eq(TaskRoom::getTaskId, taskId)
                .eq(TaskRoom::getPlaceId, placeId).eq(BaseEntity::getDelFlag, false).list();
        for (Time time : timeList) {
            taskRoomList.forEach(item -> {
                if (!ignoreTimeRoomMap.containsKey(time.getId() + "_" + item.getId())) {
                    TimeRoomVO timeRoomVO = TimeRoomVO.builder().roomId(item.getId()).timeId(time.getId()).answerType(item.getAnswerType())
                            .taskId(taskId).actualCount(0).maxCount(item.getSeatNum()).planCount(item.getSeatNum()).build();
                    // 虚拟一个id，后面用得到
                    String timeRoomId = StrUtil.isNotBlank(timeRoomIdMap.get(time.getId() + "_" + item.getId())) ? timeRoomIdMap.get(time.getId() + "_" + item.getId()) : UUIDUtils.newSortUUID();
                    timeRoomVO.setId(timeRoomId);
                    timeRoomVOList.add(timeRoomVO);
                }
            });
        }
        return timeRoomVOList;
    }
}
